Skip to main content

API structure

This page describes the conventions every endpoint of the Woofed CRM API follows. Once you understand the rules below, every endpoint in the reference reads the same way.

URL pattern

All endpoints live under a single, account‑scoped namespace:

{base_url}/api/v1/accounts/{account_id}/<resource>[/<id>][/<sub-action>]
SegmentDescription
{base_url}https://app.woofedcrm.com on the cloud, or your own host on a self‑hosted install.
api/v1API version.
accounts/{account_id}The account that owns the data. Every resource is account‑scoped.
<resource>The resource collection: contacts, deals, products, users, deal_products, deal_assignees.
<id>A specific record ID (numeric).
<sub-action>Optional sub‑action like search, upsert, or nested resources like events.

HTTP verbs

The API follows REST conventions:

VerbUsed for
GETRetrieving a single resource.
POSTCreating a resource, performing a search, or running an upsert.
PUTUpdating an existing resource (full / partial replace).
DELETERemoving a resource.

Request format

Requests that send a body always use JSON. You should always send these two headers:

Content-Type: application/json
Authorization: Bearer YOUR_TOKEN_HERE

Example body for creating a contact:

{
"full_name": "Tim Maia",
"phone": "+5541996910256",
"email": "tim@maia.com",
"custom_attributes": { "city": "RJ" },
"label_list": ["label1", "label2"]
}

Response format

Successful responses return a JSON document representing the resource (or an array of resources, for list operations). Field names use snake_case and timestamps are ISO 8601 in UTC.

{
"id": 1,
"name": "Lead site: Rubel",
"status": "open",
"stage_id": 1,
"contact_id": 1,
"custom_attributes": { "source": "Website" },
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}

Custom attributes

Most resources expose a custom_attributes JSON object. It is a free‑form key/value bag intended to store fields that don't exist as native columns (source, cpf, priority, …). The keys you store there are returned exactly as you sent them.

Datetimes

Always send dates as ISO 8601 in UTC (2025-01-20T14:00:00Z). Time‑zoned strings are not accepted by scheduled_at, done_at, won_at, lost_at, etc.

Status codes

CodeMeaning
200 OKThe request succeeded; the body contains the requested resource.
201 CreatedA resource was successfully created.
204 No ContentThe request succeeded and there is no body to return (typically DELETE).
400 Bad RequestThe request body is malformed JSON, or required fields are missing.
401 UnauthorizedMissing, malformed or expired token. See Authentication.
403 ForbiddenThe token is valid but the user has no access to that resource.
404 Not FoundThe URL is wrong, or the record does not exist in that account.
422 Unprocessable EntityValidation error — e.g. invalid email format, missing stage_id on a deal creation, etc.
429 Too Many RequestsRate limit reached — back off and retry after a short delay.
5xxServer‑side error. Retry with exponential backoff and contact support if the error persists.

Error handling

Errors return a JSON body with details about what went wrong. The common shape is:

{
"errors": {
"email": ["is invalid"],
"stage_id": ["can't be blank"]
}
}

Or for top‑level errors:

{
"error": "Unauthorized"
}

Recommended client behaviour:

  • Treat any 2xx as success.
  • Treat any 4xx as a client bug: log the request and the error body so you can fix the input.
  • Treat any 5xx as a transient failure: retry up to 3 times with exponential backoff.

Resources that expose /search (contacts, products, users — see each endpoint page) use a powerful Ransack‑style query language. You build a query object whose keys are <field>_<predicate>:

{
"query": {
"full_name_cont": "John",
"email_cont": "@example.com",
"created_at_gteq": "2025-01-01T00:00:00Z",
"id_eq": 42
}
}

Supported predicates (the most common ones):

PredicateMeaning
*_eqEquals
*_not_eqNot equal
*_contContains (substring)
*_not_contDoes not contain
*_start / *_endStarts with / ends with
*_lt / *_lteqLess than / less than or equal
*_gt / *_gteqGreater than / greater than or equal
*_in / *_not_inValue is / is not in a list
*_present / *_blankField is present / blank
*_null / *_not_nullField is null / not null
*_true / *_falseBoolean is true / false
*_matchesMatches a SQL LIKE pattern

Each predicate also accepts the suffixes _any and _all to match against a list of values:

{ "query": { "label_list_cont_any": ["vip", "trial"] } }

A complete predicate reference is shipped with each searchable endpoint.

Pagination, filtering and ordering

Most list endpoints accept query parameters for pagination and ordering. When supported, they follow these conventions:

  • page — page number (1‑based).
  • per_page — items per page (defaults vary per resource).
  • sort — field to order by (e.g. sort=created_at desc).

Each endpoint page documents the parameters that are actually supported.

Idempotency and upsert

Some resources expose an upsert action (e.g. POST /contacts/upsert, POST /deals/upsert). It will create the record if no match is found, or update the existing one otherwise. This is the pattern of choice for nightly syncs or any pipeline where the source system is the source of truth.

Next step

Browse the full reference, one resource at a time, starting with Contacts.